/**
 * Copyright © 2018 organization baomidou
 * <pre>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * <pre/>
 */
package com.baomidou.dynamic.datasource.spring.boot.autoconfigure;

import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationAdvisor;
import com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationInterceptor;
import com.baomidou.dynamic.datasource.processor.DsHeaderProcessor;
import com.baomidou.dynamic.datasource.processor.DsProcessor;
import com.baomidou.dynamic.datasource.processor.DsSessionProcessor;
import com.baomidou.dynamic.datasource.processor.DsSpelExpressionProcessor;
import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
import com.baomidou.dynamic.datasource.provider.YmlDynamicDataSourceProvider;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.druid.DruidDynamicDataSourceConfiguration;
import com.baomidou.dynamic.datasource.strategy.DynamicDataSourceStrategy;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Role;

import javax.sql.DataSource;
import java.util.Map;

/**
 * 动态数据源核心自动配置类
 *
 * @author TaoYu Kanyuxia
 * @see DynamicDataSourceProvider
 * @see DynamicDataSourceStrategy
 * @see DynamicRoutingDataSource
 * @since 1.0.0
 * 主要的功能有：
 *
 * 数据源分组，适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。
 * 内置敏感参数加密和启动初始化表结构schema数据库database。
 * 提供对Druid，Mybatis-Plus，P6sy，Jndi的快速集成。
 * 简化Druid和HikariCp配置，提供全局参数配置。
 * 提供自定义数据源来源接口(默认使用yml或properties配置)。
 * 提供项目启动后增减数据源方案。
 * 提供Mybatis环境下的 纯读写分离 方案。
 * 使用spel动态参数解析数据源，如从session，header或参数中获取数据源。（多租户架构神器）
 * 提供多层数据源嵌套切换。（ServiceA >>> ServiceB >>> ServiceC，每个Service都是不同的数据源）
 * 提供 不使用注解 而 使用 正则 或 spel 来切换数据源方案（实验性功能）。
 *
 * 在代码添加了一些注解，解释了每个bean的作用。只是单纯要了解读写分离的原理，
 * 不用去关心DynamicDataSourceProvider、DynamicDataSourceCreator这两个的代码。
 * 只需要关注DynamicRoutingDataSource、DynamicDataSourceAnnotationAdvisor、DsProcessor 、
 * DynamicDataSourceAdvisor这四个类的代码。
 * ————————————————
 * 版权声明：本文为CSDN博主「自东向西」的原创文章，遵循CC 4.0 BY-SA版权协议，转载请附上原文出处链接及本声明。
 * 原文链接：https://blog.csdn.net/zidongxiangxi/article/details/102616927
 *
 */
@Slf4j
@Configuration
@AllArgsConstructor
//DynamicDataSourceProperties 就是我们在yml可以配置的属性值，这些配置会被映射到该类对象中。
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
// 在spring-jdbc的自动配置之前先定义好datasource
//AutoConfigureBefore，为了防止和SpringBoot默认的启动器DataSourceAutoConfiguration冲突，设置该配置要在其自动配置之前进行配置。
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
// 引入druid的自动配置类
/**
 *  * Import，像容器中注入2个配置的BeanDefinition：
 *  * DruidDynamicDataSourceConfiguration：复用Druid的自动配置
 *  * DynamicDataSourceCreatorAutoConfiguration：该配置类，主要为了往容器注入DataSource创建器的bean。有4种创建器（默认，JNDI，Druid，Hikari）
 *  *
 */
@Import(value = {DruidDynamicDataSourceConfiguration.class, DynamicDataSourceCreatorAutoConfiguration.class})
//conditionalOnProperty 表明了可以通过配置spring.datasource.dynamic.enable=false来关闭动态数据源配置
@ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
public class DynamicDataSourceAutoConfiguration {

    private final DynamicDataSourceProperties properties;

    // 用于生成一个“库名->数据源”的map
    @Bean
    @ConditionalOnMissingBean
    public DynamicDataSourceProvider dynamicDataSourceProvider() {
        Map<String, DataSourceProperty> datasourceMap = properties.getDatasource();
        return new YmlDynamicDataSourceProvider(datasourceMap);
    }
    // 定义DataSource的Bean，DynamicRoutingDataSource利用DynamicDataSourceProvider生成了“库名->数据源”的map
        // 在获取真实数据源的时候，再根据ThreadLocal里的变量，决定选取map中的那个datasource
        // 注意properties.getPrimary()，这个默认是master，即默认走主库
    @Bean
    @ConditionalOnMissingBean
    public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
        DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
        dataSource.setPrimary(properties.getPrimary());
        dataSource.setStrict(properties.getStrict());
        dataSource.setStrategy(properties.getStrategy());
        dataSource.setProvider(dynamicDataSourceProvider);
        dataSource.setP6spy(properties.getP6spy());
        dataSource.setSeata(properties.getSeata());
        return dataSource;
    }
    // @Ds注解的advisor，只要在类或者方法上，增加了@Ds注解，就会被拦截：在方法执行前根据@Ds的value，往ThreadLocal设置要访问的数据源；在方法执行结束后，清除ThreadLocal中的值。
    @Role(value = BeanDefinition.ROLE_INFRASTRUCTURE)
    @Bean
    @ConditionalOnMissingBean
    public DynamicDataSourceAnnotationAdvisor dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor) {
        DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor(properties.isAllowedPublicOnly(), dsProcessor);
        DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor);
        advisor.setOrder(properties.getOrder());
        return advisor;
    }
    // 定义一个决定目标数据源的责任链，先从http的header获取，获取不到再从http的session获取，还获取不到，就会通过spel表达式来获取
    // 感觉这个有点奇怪啊，为什么要做这样的功能，由请求者指定要从哪个库读数据？？？请求者还得知道服务有哪些数据库，叫什么名字哦？？？

    @Bean
    @ConditionalOnMissingBean
    public DsProcessor dsProcessor() {
        DsHeaderProcessor headerProcessor = new DsHeaderProcessor();
        DsSessionProcessor sessionProcessor = new DsSessionProcessor();
        DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
        headerProcessor.setNextProcessor(sessionProcessor);
        sessionProcessor.setNextProcessor(spelExpressionProcessor);
        return headerProcessor;
    }
/**
 *
 * DataSource：这就是该动态数据源的实现方案，该方案中全局只有一个DataSource，是一个自定义的DataSource：DynamicRoutingDataSource。
 *
 * 该自定义DataSource本质就是内部一个Map保存所有的DataSource，然后通过primary来确定默认使用的是哪个DataSource。
 * 创建该DataSource依赖上面创建的provider。在DynamicRoutingDataSource的属性都设置完成后，将会调用其loadDataSources方法获取数据源。
 *
 * DynamicDataSourceAnnotationAdvisor：往容器注入AOP切面：主要就是Interceptor和指定PointCut，数据源的切换的核心逻辑就在这里面。
 *
 * Advisor：dynamicTransactionAdvisor，多数据源下的事务支持，早期该多数据源是不支持事务的：如果想要事务就要使用seata做分布式事务。这个下面详细讲讲。
 *
 * DsProcessor：数据源的处理器，主要是解析配置的注解内容来决定使用什么数据源。
 *
 */

/**
 *
 * 自动配置几个重要的参与对象
 *
 * creator：真正创建数据源DataSource对象的创建者，封装了将DataSourceProperty解析并创建成DataSource对象的方法。
 * provider：数据源提供者，它内部持有creator来创建对象，并暴露loadDataSources方法返回所有的数据源。
 * AOP切面：一个用来拦截数据源切换的，一个用来处理事务的。
 * processor：处理器，主要是从注解上解析出，需要切换的数据源信息。
 *
 * 链接：https://juejin.cn/post/6930579615089950733
 * 来源：稀土掘金
 * 著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
 */


}
